import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter_app/bean/file_path_entity.dart';
import 'package:flutter_app/manager/UserInfoManager.dart';
import 'package:flutter_app/network/Loading.dart';
import 'package:flutter_app/network/bean/saveChunkFile.dart';
import 'package:flutter_app/utils/ImagePickersUtils.dart';
import 'package:flutter_app/utils/logger_utils.dart';
import 'package:flutter_app/utils/md5_file_utils.dart';
import 'package:flutter_app/utils/path_utils.dart';
import 'package:flutter_app/utils/toast_utils.dart';

import 'Api.dart';
import 'ResultData.dart';
import 'bean/ChunkFile.dart';


///
/// 文件上传工具类
///  2023年3月29日
///  下雨了
///
///      整合多文件上传、大文件分片上传
///
///       upload        根据文件大小（100M）进行 多文件、大文件 区分
///
///       uploadFiles   多文件上传
///       start         大文件分片上传
///
class UploadFilesUtils {
  // 私有构造函数
  UploadFilesUtils._internal();

  // 保存单例
  static final UploadFilesUtils _uploadFiles = UploadFilesUtils._internal();

  // 工厂构造函数
  factory UploadFilesUtils() => _uploadFiles;


  // ====================================================================================================

  Future<List<FilePathEntity>> upload(List<Media> imagesList) async {
    List<FilePathEntity> allList = [];
    List<Media> aloneList = []; // 小于100M的文件
    List<Media> moreList = []; // 大于100M
    for (int i = 0; i < imagesList.length; i++) {
      int fileLength = File(imagesList[i].path!).lengthSync(); // 获取文件长度
      if (fileLength < chunkSize) {
        aloneList.add(imagesList[i]);
      } else {
        moreList.add(imagesList[i]);
      }
    }
    // 小于100M的文件
    if (aloneList.length > 0) {
      List<FilePathEntity>? list = await uploadFiles(aloneList);
      if (list != null) {
        allList.addAll(list);
      }
    }

    // 大于100M的文件
    if (moreList.length > 0) {
      for (int i = 0; i < moreList.length; i++) {
        String name = PathUtils.getName(moreList[i].path!);
        if (moreList[i].dateTime != null && moreList[i].address != null) {
          name = "${UserInfoManager().getUserName()}-${moreList[i].dateTime}-${moreList[i].address}." + name.split(".").last;
        }
        String path = moreList[i].path!;

        FilePathEntity? file = await start(path, name);
        if (file != null) {
          allList.add(file);
        }
      }

      // dialogUtils.showCustomCancelDialog(
      //   context: Get.context!,
      //   title: "提示",
      //   content: "文件已全部上传",
      //   okText: "确定",
      //   single: false,
      //   callbackOk: () {
      //     // Get.back();
      //   },
      // );
    }

    // dialogUtils.showCustomCancelDialog(
    //   context: Get.context!,
    //   title: "提示",
    //   content: "当前文件未完全上传完成，是否尝试重新上传",
    //   callbackOk: () {
    //     // start(path, fileName);
    //   },
    // );
    return allList;
  }












  // ====================================================================================================

  /// 多文件上传接口
  Future<List<FilePathEntity>?> uploadFiles(
    List<Media> imagesList, {
    Function? uploadProgress,
  }) async {
    List<FilePathEntity>? filePathEntity;
    // 解决 Bad state: Can't finalize a finalized MultipartFile. 问题
    // Dio使用post提交表单请求时，每次重新提交时，data参数都需要传入一个新的表单对象。
    List<MultipartFile> multipartFiles = [];
    imagesList.forEach((element) {
      String name = PathUtils.getName(element.path!);
      if (element.dateTime != null && element.address != null) {
        name = "${UserInfoManager().getUserName()}-${element.dateTime}-${element.address}." + name.split(".").last;
      }
      multipartFiles.add(MultipartFile.fromFileSync(element.path!, filename: name));
    });
    ResultData resultData = await Api.uploadFiles(multipartFiles,
        onSendProgress: uploadProgress != null ? (int sent, int total) {
                uploadProgress.call(sent, total);
              }
            : null);

    if (resultData.isSuccess) {
      List<dynamic> list = jsonDecode(jsonEncode(resultData.data['data']));
      filePathEntity = list.map((e) => FilePathEntity.fromJson(e)).toList();
      return filePathEntity;
    } else {
      toast.show(resultData.data["message"]);
    }
    return null;
  }







  // ====================================================================================================

  /// 分片上传  开始
  Future<FilePathEntity?> start(String path, String fileName) async {
    Loading.show(status: "分析中...");
    // 前提 对文件大小进行判断 大于200M
    // 1、获取文件 fileMd5
    var data = new DateTime.now();
    String hash = await Md5FileUtils().getFileHash(path) ?? "";
    kLog.d("hash = $hash  /  时间差 秒  = ${new DateTime.now().difference(data).inMilliseconds / 1000}");
    // 2、调用 获取文件上传和分片信息 接口
    ChunkFile? chunkFile = await getChunkFile(hash);
    if (chunkFile == null) {
      Loading.dismiss();
      return null;
    }

    bool isFinish = false;
    // if 接口信息
    if (chunkFile.fileMd5 == "") {
      //  2.1、无信息
      //    2.1.0、 对 文件进行分片
      // Loading.show(status: "分片中...");
      List<MultipartFile> list = await splitFile(path, fileName);
      //    2.1.1、 先 保存文件片信息 接口
      bool b = await saveChunkFile(list, fileName, hash);
      if (!b) {
        list.clear();
        // Loading.dismiss();
        return null;
      }
      //    2.1.2、文件进行分片 全部上传 调用分片上传 接口
      isFinish = await uploadFile(list, hash);
      //    如果没有全部上传 要弹出弹窗进行提醒是否要重试
    } else {
      //  2.2、有信息
      if (chunkFile.status == "1") {
        //    2.2.1、信息完整当前文件已经存在 终止当前程序 不再进行  return
        FilePathEntity? filePathEntity = chunkFile.fileShowVO;
        // Loading.dismiss();
        return filePathEntity;
      } else {
        //    2.2.2、信息不完整
        //      2.2.2.1、根据获取的信息 判断上传哪个分片
        List<ItemList>? itemList = chunkFile.itemList;
        if (itemList == null) {
          toast.show("分片信息不完整，请联系管理员");
          // Loading.dismiss();
          return null;
        }
        List<int> index = []; // 未上传文件の 索引
        List<MultipartFile> list = []; // 未上传の文件
        // Loading.show(status: "分片中...");
        List<MultipartFile> allList = await splitFile(path, fileName); // 对文件 进行分片
        for (var i = 0; i < itemList.length; i++) {
          ItemList item = itemList[i];
          // 判断文件详情的状态  状态 0未上传  1已上传
          if (item.status == "0") {
            // 获取 未上传文件的 索引进行保存
            index.add((item.chunkIndex ?? 0).toInt());
            kLog.d(item.chunkIndex);
          }
        }
        if (index.isEmpty) {
          isFinish = true;
        } else {
          for (var i = 0; i < index.length; i++) {
            // 根据
            list.add(allList[index[i]]);
          }
          //      2.2.2.2、调用分片上传 接口
          Loading.show(status: '上传中...');
          isFinish = await uploadFile(list, hash, index: index);
          index.clear();
          list.clear();
          allList.clear();
        }

      }
    }

    // 3、调用 合并分片 接口
    if (isFinish) {
      Loading.show();
      FilePathEntity? filePathEntity = await merge(hash);
      // 删除文件
      // await PathProviderUtils().deleteFile(path);
      // Loading.dismiss();
      return filePathEntity;
    } else {
      // Loading.dismiss();
      return null;
    }
  }

  int fileLength = 0; // 获取文件长度
  int chunkSize = 100 * 1024 * 1024; // 分片大小 这里设置 100M大小
  int allNum = 0; // 总片数

  /// 分切文件
  Future<List<MultipartFile>> splitFile(String path, String fileName) async {
    List<MultipartFile> list = [];
    File file = File(path);
    var sFile = await file.open();
    try {
      fileLength = sFile.lengthSync(); // 获取文件长度
      allNum = (fileLength / chunkSize).ceil(); // 总片数

      int x = 0; // 已经上传的长度
      while (x < fileLength) {
        // 是否是最后一片了
        bool isLast = fileLength - x >= chunkSize ? false : true;
        // 获取当前这一片的长度，最后一片可能没有设定的分片大小那么长，
        // 想象一根 56cm 长的绳子，一次只取 10cm
        // 最后一次则只能取 56 - 50 = 6cm
        int _len = isLast ? fileLength - x : chunkSize;
        // 获取一片
        List<int> postData = sFile.readSync(_len);
        // List<int> postData = sFile.readSync(_len);

        //   将分出来的 bytes 存入list中 等待上传
        // filename 为必填参数，不填写后台接收不到文件
        MultipartFile multipartFile = MultipartFile.fromBytes(postData, filename: fileName);
        // MultipartFile multipartFile = MultipartFile.fromBytes(Uint8List.fromList(postData));
        list.add(multipartFile);

        x += _len; // 记录已经取出来的长度
      }
    } finally {
      sFile.close(); // 最后一定要关闭文件
    }
    return list;
  }

  /// 获取文件上传和分片信息
  Future<ChunkFile?> getChunkFile(String fileMd5) async {
    Map<String, dynamic> param = {
      "oneParam": fileMd5, // 文件的md5码
    };
    ResultData resultData = await Api.getChunkFile(param);
    if (resultData.isSuccess) {
      ChunkFile? chunkFile = ChunkFile.fromJson(resultData.data["data"]);
      return chunkFile;
    }
    toast.show(resultData.data["message"]);
    return null;
  }

  /// 保存文件片信息
  Future<bool> saveChunkFile(List<MultipartFile> list, String fileName, String fileMd5) async {
    SaveChunkFile saveChunkFile = new SaveChunkFile();
    saveChunkFile.chunkTotal = allNum; // 文件总分片数量
    saveChunkFile.fileMd5 = fileMd5; // 文件md5码
    saveChunkFile.fileName = fileName; // 文件名称
    saveChunkFile.fileSize = fileLength / 1024; // 文件大小
    saveChunkFile.itemList = []; // 文件明细
    for (var i = 0; i < list.length; i++) {
      ItemList item = new ItemList();
      item.chunkIndex = i;
      item.chunkSize = list[i].length;
      saveChunkFile.itemList?.add(item);
    }

    ResultData resultData = await Api.saveChunkFile(saveChunkFile.toJson());
    if (resultData.isSuccess) {
      String data = resultData.data["data"];
      return true;
    }
    toast.show(resultData.data["message"]);
    return false;
  }

  /// 分片上传
  Future<bool> uploadFile(List<MultipartFile> list, String fileMd5, {List<int>? index}) async {
    int chunkIndex = 0; // 传到第几片了
    Loading.show(status: "上传中...");
    for (var i = 0; i < list.length; i++) {
      FormData formData = FormData.fromMap({
        'file': list[i],
        'chunk': index == null ? i : index[i], // 第几片
        'fileMd5': fileMd5, // 文件的md5码
      });

      ResultData resultData = await Api.upload(formData);
      if (resultData.isSuccess) {
        chunkIndex++;
        if (chunkIndex == list.length) {
          Loading.dismiss();
          return true;
        }
      } else {
        toast.show(resultData.data["message"]);
      }
    }
    Loading.dismiss();
    return false;
  }


  /// 合并分片
  Future<FilePathEntity?> merge(String fileMd5) async {
    Map<String, dynamic> param = {
      "oneParam": fileMd5, // 文件的md5码
    };
    ResultData resultData = await Api.merge(param);
    if (resultData.isSuccess) {
      FilePathEntity filePathEntity = FilePathEntity.fromJson(resultData.data['data']);
      return filePathEntity;
    }
    toast.show(resultData.data["message"]);
    return null;
  }
}
